

#include "luaos_lua.h"
#include "luaos_allotor.h"
#include "luaos_profiler.h"

/* open lua libs */
#include "extend/lua_compile.h"
#include "extend/lua_json.h"
#include "extend/lua_list.h"
#include "extend/lua_bitmap.h"
#include "extend/lua_skiplist.h"
#include "extend/lua_deflate.h"
#include "extend/lua_http.h"
#include "extend/lua_openssl.h"
#include "extend/lua_string.h"
#include "extend/lua_lucky.h"
#include "extend/lua_storage.h"
#include "extend/lua_navigate.h"
#include "extend/lua_directory.h"

/********************************************************************************/

static const luaL_Module luaos_modules[] = {
  LUAOPEN_MODULE(luaopen_compile),
  LUAOPEN_MODULE(luaopen_json),
  LUAOPEN_MODULE(luaopen_list),
  LUAOPEN_MODULE(luaopen_bitmap),
  LUAOPEN_MODULE(luaopen_http),
  LUAOPEN_MODULE(luaopen_skiplist),
  LUAOPEN_MODULE(luaopen_deflate),
  LUAOPEN_MODULE(luaopen_base64),
  LUAOPEN_MODULE(luaopen_crypto),
  LUAOPEN_MODULE(luaopen_lstring),
  LUAOPEN_MODULE(luaopen_storage),
  LUAOPEN_MODULE(luaopen_directory),
  LUAOPEN_MODULE(luaopen_core),
  LUAOPEN_MODULE(luaopen_lucky),
  LUAOPEN_MODULE(luaopen_navigate),
  LUAOPEN_MODULE(luaopen_profiler),
  LUAOPEN_MODULE(NULL)
};

/********************************************************************************/

static int  panic(lua_State* L);
static void warnfoff (void *ud, const char *message, int tocont);
static void warnfon  (void *ud, const char *message, int tocont);
static void warnfcont(void *ud, const char *message, int tocont);

/********************************************************************************/

static bool by_require(lua_State* L) {
  lua_Debug ar;
  if (!lua_getstack(L, 2, &ar)) {
    return false;
  }
  if (!lua_getinfo(L, "Sln", &ar)) {
    return false;
  }
  return (ar.name != nullptr);
}

static int luac_start(lua_State* L) {
  static thread_local bool loaded = false;
  if (loaded || by_require(L)) {
    return 0;
  }
  luaL_checktype(L, 1, LUA_TFUNCTION);
  luaL_checktype(L, 2, LUA_TSTRING);
  lua_remove(L, 2); // remove filename
  int params = lua_gettop(L) - 1;
  if (lua_pcall(L, params, 0, 0) != LUA_OK) {
    lua_error(L);
  }
  loaded = true;
  return LUA_OK;
}

static void openlibs(lua_State* L, const luaL_Module f[]) {
  lua_gc(L, LUA_GCSTOP);
  luaL_openlibs(L);
  for (; f && f->func; f++) {
    lua_pushcfunction(L, f->func);
    if (lua_pcall(L, 0, 0, 0) != LUA_OK) {
      lua_error(L);
    }
#if defined(LUA_DEBUG)
    int top = lua_gettop(L);
    if (top > 0) {
      lua_ferror("%s: stack top index error, top = %d", f->name, top);
    }
#endif
  }
  const luaL_Reg methods[] = {
    { "start",  luac_start  },
    { NULL,     NULL        }
  };
  new_module(L, "luaos", methods);
  lua_gc(L, LUA_GCRESTART);
  lua_gc(L, LUA_GCGEN, 0, 0);
}

static lua_State* newstate() {
  lua_State* L = lua_newstate(luaos_allotor, NULL);
  if (L) {
    lua_atpanic (L, &panic);
    lua_setwarnf(L, warnfoff, L); /* default is warnings off */
    openlibs(L, luaos_modules);
  }
  return L;
}

static bool check_debuger(lua_State* L) {
#if !defined(_MSC_VER)
  return false;
#elif defined(LUA_DEBUG)
  return true;
#else
  return lua_gethookmask(L) ? true : false;
#endif
}

LUAOS_API bool is_debugging(lua_State* L) {
  static thread_local bool last_check = false;
  auto debugging = check_debuger(L);
  if (debugging != last_check) {
    lua_ftrace("WARNING: Lua debug is %s\n", last_check ? "disabled" : "enabled");
  }
  return (last_check = debugging);
}

LUAOS_API lua_State* luaos_state() {
  static thread_local lua_State* L = newstate();
  return L;
}

LUAOS_API typeof<io::service> luaos_service() {
  return io::service::local();
}

/********************************************************************************/

static int panic(lua_State *L) {
  const char *msg = lua_tostring(L, -1);
  if (msg == NULL) {
    msg = "error object is not a string";
  }
  lua_writestringerror("PANIC: unprotected error in call to Lua API (%s)\n", msg);
  return 0;
}

static int checkcontrol(lua_State *L, const char *message, int tocont) {
  if (tocont || *(message++) != '@')  /* not a control message? */
    return 0;
  else {
    if (strcmp(message, "off") == 0)
      lua_setwarnf(L, warnfoff, L);  /* turn warnings off */
    else if (strcmp(message, "on") == 0)
      lua_setwarnf(L, warnfon, L);   /* turn warnings on */
    return 1;  /* it was a control message */
  }
}

static void warnfcont(void *ud, const char *message, int tocont) {
  lua_State *L = (lua_State *)ud;
  lua_writestringerror("%s", message);  /* write message */
  if (tocont) { /* not the last part? */
    lua_setwarnf(L, warnfcont, L);  /* to be continued */
  }
  else {  /* last part */
    lua_writestringerror("%s", "\n");  /* finish message with end-of-line */
    lua_setwarnf(L, warnfon, L);  /* next call is a new message */
  }
}

static void warnfon(void *ud, const char *message, int tocont) {
  if (checkcontrol((lua_State*)ud, message, tocont)) { /* control message? */
    return;  /* nothing else to be done */
  }
  lua_writestringerror("%s", "Lua warning: ");  /* start a new warning */
  warnfcont(ud, message, tocont);  /* finish processing */
}

static void warnfoff(void *ud, const char *message, int tocont) {
  checkcontrol((lua_State *)ud, message, tocont);
}

/********************************************************************************/
